package com.cy;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 当我们的主启动类使用@EnableFeignClients注解描述时，spring工程在启动时
 * 会扫描@EnableFeignClients注解描述的接口，并基于接口创建实现类（代理类）
 */
@EnableFeignClients
@SpringBootApplication
public class ScaConsumerApplication {
    //创建日志对象 --可以打印方法所接收到的数据
    private static Logger log=
            LoggerFactory.getLogger(ScaConsumerApplication.class);
    //主启动类
    public static void main(String[] args) {
        SpringApplication.run(ScaConsumerApplication.class,args);
        log.debug("==debug==");//debug<info<warn<error
        log.info("==ifo==");
        log.warn("==warn==");
        log.error("==error==");

    }

    /**
     * spring boot 工程中可以使用此对象调用第三方服务
     * @return
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    /**
     * @Bean 注解由spring提供，通常用于描述方法，用于告诉spring框架
     * 此方法的返回值要交给spring管理  类似于@Controller，@Service ，@Component注解（
     * 这些注解一般描述类）
     * @Bean注解一般写在配置类
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate loadBalancerRestTemplate(){
        return new RestTemplate();
    }

    /**
     * 定义一个资源访问类，测试sentinel的链路模式进行限流
     */
    @Service
    public class ScaConsumerService{
        @SentinelResource("doConsumerService")
        public String doConsumerService(){
            //表示通过此方法访问指定资源，例如以后访问数据库
            return "do consumer service";
        }
    }

    @RestController
    public class ScaConsumerController{
        //注入上面写好的资源
        @Autowired
        private ScaConsumerService scaConsumerService;
        //名字从application.yml文件中获取
        @Value("${spring.application.name}")
        //消费方的名字
        private String consumerName;

        @Autowired
        private RestTemplate restTemplate;
        @Autowired
        private RestTemplate loadBalancerRestTemplate;

        //http:localhost:8090/consumer/doRestEcho01
        @GetMapping("/consumer/doRestEcho01")
        public String doRestEcho01(){
            //日志输出
//            log.debug("/consumer/doRestEcho01  {}","doRestEcho01");
            //定义服务提供方的地址
            String url="http://localhost:8081/provider/echo/"+consumerName;
            return restTemplate.getForObject(url,String.class);
        }

        //LoadBalancerClient  此对象底层基于Ribbon实现负载均衡
        //LoadBalancerClient 此对象在服务启动时底层已经帮我们创建好了
        @Autowired
        private LoadBalancerClient loadBalancerClient; //客服端的一种负载均衡算法的实现方式
        //http:localhost:8090/consumer/doRestEcho02
        @GetMapping("/consumer/doRestEcho02")
        public String doRestEcho02(){
            //基于服务名采用一定负载均衡算法获取服务实例
            ServiceInstance choose = loadBalancerClient.choose("sca-provider");
            String ip = choose.getHost();
            /*
             * 日志界别的使用以及好处：
             * log.debug(ip); 相当于打印语句System.out.println(ip);
             * 把结果打印到控制台，项目上线时把配置文件里面的日志级别提高到error，
             * 项目中的打印语句就不会打印，相比System.out.println(ip);打印语句的好处：
             * 项目上线过程中直接提高日志级别为error其他级别的打印语句就不会打印
             * 如果是System.out.println(ip);打印语句项目上线的过程时还要逐句进行注释
             * 否则影响运行性能，而且项目日后有问题不利于查找问题所在
             * 如果是日志级别打印，在维护过程中直接将配置文件中的日志打印级别改为debug即可查找问题
             */
            log.debug(ip);
            System.out.println(ip);
            int port = choose.getPort();
            //定义服务提供方的地址 --方式一：字符串拼接
            //String url="http://"+ip+":"+port+"/provider/echo/"+consumerName;
            //定义服务提供方的地址 --方式二： 使用占位符的方式（%s）
            //注意：使用占位符时   %s为占位符  传值的时候一定要注意顺序
            String url = String.format("http://%s:%s/provider/echo/%s",ip,port,consumerName);
            //调用服务提供方(sca-provider)
            return restTemplate.getForObject(url,String.class);
        }

        //创建一个可实现自增自减功能的一个对象 -- 线程安全
        private AtomicLong atomicLong = new AtomicLong(0);
        @GetMapping("/consumer/doRestEcho03")
        public String doRestEcho03(HttpServletRequest request) throws InterruptedException {
            String header = request.getHeader("X-Request-Foo");
            System.out.println("header = "+header);
            String parameter = request.getParameter("foo");
            System.out.println("parameter = "+parameter);

//            public String doRestEcho03(HttpServletResponse response) throws InterruptedException {
//            Cookie cookie = new Cookie("cookieId", "ABCD");
//            response.addCookie(cookie);
            //sentinel限 -- 慢调用比例的分析
            /*
            我们也可以进行断点调试，ctrl + N 在DefaultBlockExceptionHandler中
            的handle方法内部加断点，分析异常类型，假如异常类型为DegradeException
            则为降级熔断。
             */
//            long num = atomicLong.getAndIncrement();
//            if(num%2==0) {
//                Thread.sleep(200);//模拟耗时操作
//            }

            //sentinel限流 -- 流控规则中的链路限流的调用分析
            //scaConsumerService.doConsumerService();

            /*
            定义服务提供方的地址 --方式二： 使用占位符的方式（%s）
            注意：使用占位符时   %s为占位符  传值的时候一定要注意顺序
            直接基于服务名进行服务的调用
             */
            String url = String.format("http://%s/provider/echo/%s","sca-provider",consumerName);
            //调用服务提供方(sca-provider)
            return loadBalancerRestTemplate.getForObject(url,String.class);
        }

        /**
         * sentinel限流 -- 热点业务
         * @param id
         * @param name
         * @return
         */
        @GetMapping("/consumer/doRestEcho04")
        @SentinelResource
        /*
            @RequestParam注解的解释：
                1、只要写了这个注解，表示一定要给后面的参数传值，不传值会报错。
                2、如果不传值就必须加上(required = false)，表示可以不传值也可
                以不传值，默认为true
         */
        //http://localhost:8090/consumer/doRestEcho04?id=10&name=jack 可以基于参数0，也就是id进行限流
        public String doRestEcho03(@RequestParam(required = false) Integer id,
                                   @RequestParam(required = false) String name){
            //字符串拼接方式不用加号拼接（推荐使用）
            // %d表示：数字的占位符，%s表示：字符串的占位符
            return String.format("request id=%d,name=%s",id,name);
            //字符串拼接方式用加号拼接
//            return "request id is "+id;
        }


    }
}
